﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Aga.GestionDeArchivos.Domain.Entities;
using Aga.GestionDeArchivos.Common;
using Aga.GestionDeArchivos.Storage;
using System.Configuration;

namespace Aga.GestionDeArchivos.Services
{
    public class UserService : DomainServiceBase<User>
    {
        public static int MinPasswordLength = 6;

        protected override RepositoryBase<User> Repository
        {
            get { return RepositoryFactory.CreateUserRepository(); }
        }

        /// <summary>
        /// Registers the new user.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <param name="passwordConfirmation">The password confirm.</param>
        /// <param name="email">The email.</param>
        /// <param name="firstName">The first name.</param>
        /// <param name="lastName">The last name.</param>
        /// <returns></returns>
        public User RegisterNewUser(
            string userName,
            string password,
            string passwordConfirmation,
            string email,
            string emailConfirmation,
            string firstName,
            string lastName)
        {
            // Check if name is available
            if (!CheckIfNameIsAvailable(userName))
                throw new ArgumentException("El nombre de usuario ya está en uso.");

            if (password.Length < MinPasswordLength)
                throw new ArgumentException(String.Format("La contraseña debe ser mayor a {0} caracteres.", MinPasswordLength));

            // Verify if passwords do match
            password = password.Trim();
            passwordConfirmation = passwordConfirmation.Trim();

            if (!password.Equals(passwordConfirmation))
                throw new ArgumentException("La contraseña y su confirmación no coinciden.");

            // Verify if emails do match
            email = email.Trim();
            emailConfirmation = emailConfirmation.Trim();

            if (!email.Equals(emailConfirmation))
                throw new ArgumentException("El Email y su confirmación no coinciden.");

            User user = new User
            {
                Username = userName,
                Password = MD5Helper.GetMD5Hash(password),
                EMail = email,
                FirstName = firstName,
                LastName = lastName
            };

            user = Save(user);

            return user;
        }

        /// <summary>
        /// Validates the user.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <returns></returns>
        public User ValidateUser(string userName, string password)
        {
            User user;

            if (userName == "admin")
                user = GetAdminUser();
            else
                user = GetByUserName(userName);

            if (user == null)
                throw new ArgumentException("Usuario no encontrado: " + userName);

            return ValidateUser(user, password);
        }

        /// <summary>
        /// Gets the admin user.
        /// </summary>
        /// <returns></returns>
        private User GetAdminUser()
        {
            User user = GetByUserName("admin");

            if (user == null)
            {
                user = new User
                {
                    Id = 0,
                    Username = "admin",
                    Password = ConfigurationManager.AppSettings["DefaultAdminPassword"],
                    FirstName = "Administrador",
                    EMail = ConfigurationManager.AppSettings["DefaultAdminEMail"]
                };

                user = Save(user);
            }

            return user;
        }

        /// <summary>
        /// Validates the user.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <returns></returns>
        private User ValidateUser(User user, string password)
        {
            string hashedPassword = MD5Helper.GetMD5Hash(password);

            if (user.Password != hashedPassword)
                throw new ArgumentException("Contraseña inválida.");

            return user;
        }

        /// <summary>
        /// Checks if name is available.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <returns></returns>
        public bool CheckIfNameIsAvailable(string userName)
        {
            if (userName.Equals("admin")) // 'admin' is not available for anyone
                return false;

            return !GetAll().Any( u => u.Username == userName);
        }

        /// <summary>
        /// Gets the name of the by user.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <returns></returns>
        public User GetByUserName(string userName)
        {
            return GetAll()
                   .Where(u => u.Username.Equals(userName))
                   .FirstOrDefault();
        }

        /// <summary>
        /// Changes the password.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="oldPassword">The old password.</param>
        /// <param name="newPassword">The new password.</param>
        /// <param name="newPasswordConfirmation">The new password confirmation.</param>
        /// <returns></returns>
        public bool ChangePassword(string userName, string oldPassword, string newPassword, string newPasswordConfirmation)
        {
            User user = GetByUserName(userName);

            ValidateUser(user, oldPassword);

            if (String.Compare(newPassword, newPasswordConfirmation) != 0)
                throw new ArgumentException("La contraseña nueva y su confirmación no coinciden.");

            if (newPassword.Length < MinPasswordLength)
                throw new ArgumentException(String.Format("La contraseña debe ser mayor a {0} caracteres.", MinPasswordLength));

            user.Password = MD5Helper.GetMD5Hash(newPassword);

            Save(user);

            return true;
        }

        /// <summary>
        /// Adds the roles to the user.
        /// </summary>
        /// <param name="userId">The user id.</param>
        /// <param name="rolesId">The roles id.</param>
        /// <returns></returns>
        public User AddRoles(int userId, int[] rolesId)
        {
            var roles = RepositoryFactory.CreateRoleRepository().GetById(rolesId).ToList();

            var user = RepositoryFactory.CreateUserRepository().GetById(userId);

            var rolesToAdd = roles.Except(user.Roles);

            foreach (var role in rolesToAdd)
                user.Roles.Add(role);

            RepositoryFactory.CreateUserRepository().Save(user);

            return user;
        }

        /// <summary>
        /// Removes the roles.
        /// </summary>
        /// <param name="userId">The user id.</param>
        /// <param name="rolesId">The roles id.</param>
        /// <returns></returns>
        public User RemoveRoles(int userId, int[] rolesId)
        {
            var roles = RepositoryFactory.CreateRoleRepository().GetById(rolesId).ToList();

            var user = RepositoryFactory.CreateUserRepository().GetById(userId);

            var rolesToRemove = user.Roles.Intersect(roles).ToList();

            foreach (var role in rolesToRemove)
                user.Roles.Remove(role);

            RepositoryFactory.CreateUserRepository().Save(user);

            return user;
        }

        /// <summary>
        /// Gets all users.
        /// </summary>
        /// <returns></returns>
        public override IQueryable<User> GetAll()
        {
            var users = base.GetAll();

            users.Where(u => u.Username != "admin");

            return users;
        }

        public PagedList<User> GetPage(int pageIndex, int pageSize)
        {
            return GetPage(GetAll(), pageIndex, pageSize);
        }

        public PagedList<User> GetPage(IQueryable<User> allItems, int pageIndex, int pageSize)
        {
            var items = allItems.OrderBy(c => c.FirstName).Skip(pageIndex * pageSize).Take(pageSize).ToList();

            int totalCount = allItems.Count();
            int pageCount = Convert.ToInt32(Math.Ceiling((double)totalCount / pageSize));

            return new PagedList<User>(items, pageIndex, pageCount, pageSize, totalCount);
        }

        /// <summary>
        /// Gets the users roles.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public IEnumerable<Role> GetUsersRoles(int id)
        {
            var user = RepositoryFactory.CreateUserRepository().GetById(id);

            return  user.Roles;
        }

        /// <summary>
        /// Checks if the user contains at least a role with the action allowed.
        /// </summary>
        /// <param name="userId">The user id.</param>
        /// <param name="actionId">The action id.</param>
        /// <returns></returns>
        public bool UserCan(int userId, int actionId)
        {
            var user = RepositoryFactory.CreateUserRepository().GetById(userId);

            if (user.Username.Equals("admin")) // It is the admin
                return true;

            var allowedActions = user.Roles.SelectMany(r => r.Actions);

            return allowedActions.Any(a => a.ActionId.Equals(actionId));
        }

        /// <summary>
        /// Users the can.
        /// </summary>
        /// <param name="userId">The user id.</param>
        /// <param name="actions">The actions.</param>
        /// <returns></returns>
        public bool UserCan(int userId, int[] actions)
        {
            var user = RepositoryFactory.CreateUserRepository().GetById(userId);

            if (user.Username.Equals("admin")) // It is the admin
                return true;

            var allowedActions = user.Roles.SelectMany(r => r.Actions);

            return allowedActions.Any(a => actions.Contains(a.ActionId));
        }

        /// <summary>
        /// Users the can.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="Actions">The actions.</param>
        /// <returns></returns>
        public bool UserCan(int userId, Actions actions)
        {
            var values = actions.ToString()
                .Split(new[] { ", " }, StringSplitOptions.None)
                .Select(v => (int)Enum.Parse(typeof(Actions), v))
                .ToArray();

            return UserCan(userId, values);
        }
    }
}
